package gameclasses;

import interfaces.IBuilding;
import interfaces.ILogging;
import interfaces.IUnit;
import interfaces.IObservable;
import interfaces.IObserver;

import java.util.NoSuchElementException;
import java.util.Vector;

import datastructures.Vector2f;

import exceptions.Exception;
import exceptions.IllegalPlayerCountException;

public class GameManager implements IObservable {

	/* GAME CONSTANTS */
	private final int STARTING_MONEY = 15000;
	private final int KILL_POINTS = 5;
	private final int ROUND_BONUS_POINTS = 3;

	private ILogging logger;

	private Vector<IUnit> units;
	private Vector<IBuilding> buildings;

	// private Random r = new Random();
	private Map map;

	private int playercount;
	private int activePlayer;
	private Player[] players;

	private long selectedUnit = -1;

	private Vector<IObserver> observer = new Vector<IObserver>();

	/**
	 * Inizializes the game (create player/map/startressources)
	 * and displays it in the console
	 * 
	 * @param playercount count of players ingame
	 * @param mapsize size of the map
	 * @param logger chose the Log to use
	 */	
	public GameManager(int playercount, Vector2f mapsize, ILogging logger) {
		
		try{
			this.logger = logger;
		
			logger.addEntry("GameManager", "Spiel wird initialisiert...");
			logger.addEntry("GameManager", playercount + "-Spieler Spiel gestartet.");

			map = Map.createTemplateMap(0, 2);

			logger.addEntry("GameManager", "Karte mit der Groesse " + map.getWidth() + "x" + map.getHeight()+ " wurde erzeugt.");

			this.playercount = playercount;
			players = new Player[playercount];
			for (int i = 0; i < playercount; i++) {
				Vector2f p = map.getSpawnPoint(i);
				players[i] = new Player(this,i,STARTING_MONEY, p);
				logger.addEntry("GameManager", "Spieler " + i + " wurde erzeugt, Spawnpunkt: (" + p.x()+ "|" + p.y() + ")");
			}
			activePlayer = 0;
			players[activePlayer].addPoints(ROUND_BONUS_POINTS);
			logger.addEntry("GameManager", "Spieler wurden initialisiert, es gibt " + playercount+ " Spieler! Aktueller Spieler: " + activePlayer);

			units = new Vector<IUnit>(20);
		

			NotifyAll();
		}
		catch (IllegalPlayerCountException e) {
			e.printStackTrace();
			logger.addEntry("GameManager", "Karte konnte nicht erzeugt werden, bitte Spiel neustarten!");
		}
	}

	/**
	 * Changes the actionpermission to the next player,
	 * gives points for the round
	 * and displays it in the console
	 */
	public void nextPlayer() {
		checkGameOver();
		players[activePlayer].addPoints(ROUND_BONUS_POINTS);
		do{
			activePlayer++;
			activePlayer %= playercount;
		}
		while (players[activePlayer].isGameOver()==Exception.PLAYER_GAME_OVER);
		selectedUnit = -1;
		logger.addEntry("GameManager", "Spieler wurde gewechselt! Aktueller Spieler: " + activePlayer);
		NotifyAll();
	}
	/**
	 * 
	 * @return Returns the used Logger
	 */
	public ILogging getLogger()
	{
		return this.logger;
	}
	/**
	 * 
	 * @return Returns the active player's ID
	 */
	public int getActivePlayerID() {
		return activePlayer;
	}
	/**
	 * 
	 * @return Returns the active player
	 */
	public Player getActivePlayer() {
		return players[activePlayer];
	}
	/**
	 * Adds an Observer
	 * @param o Observer to be added
	 */
	@Override
	public void addObserver(IObserver o) {
		observer.add(o);
		logger.addEntry("GameManager", "Observer wurde hinzugefuegt!");
	}
	/**
	 * Removes an Observer
	 * @param o Observer to be removed
	 */
	@Override
	public void removeObserver(IObserver o) {
		observer.remove(o);
		logger.addEntry("GameManager", "Observer wurde entfernt!");
	}
	/**
	 * Tells all Observer to update
	 */
	@Override
	public void NotifyAll() {
		for (IObserver o : observer) {
			o.Update();
		}

	}
	/**
	 * Buys a Unit
	 * @return Succes or not
	 */
	public int buyUnit() {
		if (players[activePlayer].getMoney() >= 1000) {
			for (IUnit u : units) {
				if (u.getPosition().equals(players[activePlayer].getUnitSpawn())) {
					logger.addEntry("GameManager", "Kann Einheit nicht kaufen, Position blockiert");
					return Exception.POSITION_BLOCKED;
				}
			}
			IUnit u = Tank.generateTemplateTank(this, activePlayer, players[activePlayer].getUnitSpawn(), 0);
			players[activePlayer].pay(1000);
			units.add(u);
			logger.addEntry("GameManager", "Spieler " + activePlayer + " hat eine Einheit (Panzer, ID: " + u.getID() + ") gekauft. Neuer Kontostand: " + players[activePlayer].getMoney());
			NotifyAll();
			return Exception.SUCCESSFULLY_BOUGHT;
		} else
			logger.addEntry("GameManager", "Spieler " + activePlayer + " hat nicht genug Geld, Einheit wurde nicht gekauft");
			return Exception.PLAYER_OUT_OF_MONEY;
	}
	/**
	 * Removes the specified Unit
	 * @param unit The Unit which will be removed
	 */
	public void removeUnit(IUnit unit) {
		if(units.remove(unit))
			logger.addEntry("GameManager", "Einheit mit der ID " + unit.getID() + " wurde geloescht.");
	}
	/**
	 * Removes the specified Building
	 * @param building The Building which will be removed
	 */
	public void removeBuilding(IBuilding building){
		if (buildings.remove(building))
			logger.addEntry("GameManager", "Gebaeude mit der ID " +  building.getID() + " wurde geloescht.");
	}
	/**
	 * Displays the Units of,
	 * -2 all players
	 * -1 the current player 
	 * @param playerID Player whose Units will be displayed
	 * @return List of the Units
	 */
	public Vector<IUnit> getUnitsOf(int playerID) {
		if (playerID == -2) {
			logger.addEntry("GameManager", "Einheiten aller Spieler wurden angefordert");
			return units;
		}

		if (playerID == -1)
		{
			playerID = activePlayer;
		}
		

		Vector<IUnit> tmp = new Vector<IUnit>();

		for (IUnit u : units) {
			if (u.getPlayerID() == playerID) {
				tmp.add(u);
			}
		}
		logger.addEntry("GameManager", "Einheiten von Spieler " + playerID +" wurden angefordert");
		return tmp;
	}
	/**
	 * Selects the Unit on {@link Point} p
	 * @param p {@link Point} on which the Unit you want to select is standing
	 * @return errorcode from {@link Exception}
	 */
	public int selectUnit(Vector2f p) {
		for (IUnit u : units) {
			if (u.getPosition().equals(p) && (u.getPlayerID() == activePlayer)) {
				selectedUnit = u.getID();
				logger.addEntry("GameManager", "Einheit " + u.toString() + " wurde ausgewaehlt");
				return Exception.NO_ERROR_CODE;
			}
		}
		logger.addEntry("GameManager", "Keine Einheit an diesem Punkt " + p.toString() + " gefunden");
		return Exception.NO_UNIT_SELECTED;
	}

	/**
	 *  Tells the selected Unit to either move to the Point p or shoot at the enemy
	 *  at Point p if there is one
	 * @param p Point on which the action should happen
	 * @return errorcode 10X for unitaction
	 */
	public int unitAction(Vector2f p) {
		if (selectedUnit >= 0) {
			IUnit actor = getUnitByID(selectedUnit);
			try{
				IUnit u = findUnitAt(p);
				if (actor.getID() != u.getID()) {
					int errcode = actor.shoot(u);
					logger.addEntry("GameManager", "Einheit " + actor.getID() + " schie�t auf Einheit " + u.getID() + ". Status des Opfers: " + u.getStatus());
					if (errcode == Exception.TARGET_DESTROYED)
						players[activePlayer].addPoints(KILL_POINTS);
					NotifyAll();
					return errcode;
				}
				logger.addEntry("GameManager", "Einheit " + actor.getID() + "schie�t nicht auf sich selber");
				return Exception.FRIENDLY_FIRE;
			}
			catch (NoSuchElementException e) {
				int ec = actor.move(p);
				if (ec == Exception.MOVING) {
					logger.addEntry("GameManager", "Einheit " + actor.getID() + " f�hrt nach ("
							+ actor.getPosition().x() + "|" + actor.getPosition().y() + ")");
					NotifyAll();
				}
				return ec;
			}

		}
		logger.addEntry("GameManager", "Keine Einheit selektiert!");
		return Exception.NO_UNIT_SELECTED;
	}
	/**
	 * Adds a specific text to the Log
	 * @param s Text to add to the Log
	 */
	public void addToLog(String s)
	{
		logger.addEntry("GameManager", s);
	}


	private IUnit getUnitByID(long selectedUnit2) {
		for (IUnit i : units) {
			if (i.getID() == selectedUnit2) {
				return i;
			}
		}
		return null;
	}


	/** 
	 * @return Current map
	 */
	public Map getMap()
	{
		return this.map;
	}
	
	/**
	 * Finds and returns a unit at Point p
	 * @param p {@link Point}
	 * @return The {@link IUnit} at the passed {@link Point}
	 * @throws NoSuchElementException If there is no Unit at the given Point 
	 */
	public IUnit findUnitAt(Vector2f p) throws NoSuchElementException
	{
		for (IUnit u : units) {
			if (u.getPosition().equals(p)) {
				return u;
			}
		}
		throw new NoSuchElementException("Keine Einheit an diesem Punkt gefunden!");
	}
	

	private int checkGameOver()
	{
		int count = 0;
		for(int i = 0; i< playercount; i++)
		{
			if (players[i].checkGameOver()==Exception.PLAYER_GAME_OVER)
			{
				count++;
				logger.addEntry("GameManager", "Spieler "+i+" ist GameOver!");
			}
		}
		return count;
	}
	
	/**
	 * 
	 * @param p {@link Player}
	 * @deprecated This function is not yet implemented, should be comming with version 0.4.1
	 */
	public void killPlayer(Player p)
	{
		
	}
}

